/**
 * @file tal_uart.c
 * @author www.tuya.com
 * @brief tal_uart module is used to 
 * @version 0.1
 * @date 2022-11-10
 *
 * @copyright Copyright (c) tuya.inc 2022
 *
 */

#include "tuya_ringbuf.h"
#include "tuya_slist.h"
#include "tal_mutex.h"
#include "tal_semaphore.h"
#include "tal_log.h"
#include "tal_memory.h"
#include "tkl_init_common.h"
#include "tkl_uart.h"

#include "tal_uart.h"

/***********************************************************
************************macro define************************
***********************************************************/


/***********************************************************
***********************typedef define***********************
***********************************************************/
typedef struct uart_dev_node {
    SLIST_HEAD node;
    UINT32_T port_num;
    UINT32_T open_mode;
    SEM_HANDLE rx_ring_sem;
    TUYA_RINGBUFF_T rx_ring;
#ifdef CONFIG_UART_ASYNC_WRITE
    SEM_HANDLE tx_ring_sem;
    TUYA_RINGBUFF_T tx_ring;
#endif
    UINT16_T wait_rx_flag;
    UINT16_T wait_tx_flag;
    SEM_HANDLE rx_block_sem;
    SEM_HANDLE tx_block_sem;
} TAL_UART_DEV;

struct single_mutext_list {
    MUTEX_HANDLE mutex;
    SLIST_HEAD head;
};

/***********************************************************
********************function declaration********************
***********************************************************/


/***********************************************************
***********************variable define**********************
***********************************************************/
struct single_mutext_list g_uart_list;

/***********************************************************
***********************function define**********************
***********************************************************/

typedef void (*UART_ISR_CALL_BACK)(void *);

TAL_UART_DEV *uart_list_get_one_node(TUYA_UART_NUM_E port_num)
{
    SLIST_HEAD *node_index = &g_uart_list.head;
    TAL_UART_DEV *uart_dev;

    while(node_index->next != NULL) {
        uart_dev = (TAL_UART_DEV *)node_index->next;
        if (uart_dev->port_num == port_num) {
            return uart_dev;
        }
        node_index = node_index->next;
    }

    return NULL;
}


OPERATE_RET uart_list_add_one_node(TAL_UART_DEV *uart_info)
{
    OPERATE_RET ret = tal_mutex_lock(g_uart_list.mutex);
    if (ret != OPRT_OK) {
        return ret;
    }
    tuya_slist_add_head(&g_uart_list.head, &uart_info->node);

    tal_mutex_unlock(g_uart_list.mutex);
    return OPRT_OK;
}


OPERATE_RET uart_list_delete_one_node(TAL_UART_DEV *uart_info)
{
    OPERATE_RET ret = tal_mutex_lock(g_uart_list.mutex);
    if (ret != OPRT_OK) {
        return ret;
    }

    tuya_slist_del(&g_uart_list.head, &uart_info->node);

    tal_mutex_unlock(g_uart_list.mutex);
    return OPRT_OK;
}


#ifdef CONFIG_UART_ASYNC_WRITE
VOID_T uart_tx_chars_in_isr(UINT32_T port_num)
{
    TAL_UART_DEV *uart_info = uart_list_get_one_node(port_num);
    if (uart_info == NULL) { /* 已初始化，返回错误 */
        return;
    }

    ring_buffer_s *tx_ring = uart_info->tx_ring;
    UINT8_T tx_byte;
    UINT8_T tx_count = 0;

    while(1) {
        ret = tuya_ring_buff_read(tx_ring, &tx_byte, 1);
        if (ret != 1) {
            break;
        }
        
        ret = tkl_uart_write(port_num, &tx_byte, 1);
        if (ret != 1) {
            break;
        }

        tx_count++;
    }

    if ((uart_info->open_mode & O_BLOCK) && (tx_count > 0)) {
        if (uart_info->wait_rx_flag == TRUE) {
            uart_info->wait_rx_flag = FALSE;
            tal_semaphore_post(uart_info->tx_block_sem);
        }
    }

}
#endif


void uart_rx_chars_in_isr(TUYA_UART_NUM_E port_num)
{
    TAL_UART_DEV *uart_info = uart_list_get_one_node(port_num);
    if (uart_info == NULL) {
        return;
    }

    UINT8_T rx_char;
    INT_T ret = 0;
    UINT32_T rx_bytes = 0;

    /*
     * 软件缓冲区满，读取到的数据不会写到软件缓冲区中
     * 但是会继续读取硬件缓冲区的内容，直到读空为止
     */
    while(1) {
        ret = tkl_uart_read(port_num, &rx_char, 1);
        if (ret != 1) {
            break;
        }

        ret = tuya_ring_buff_write(uart_info->rx_ring, &rx_char, 1);
        if (ret != 1) {
            break;
        }

        rx_bytes++;
    }

#ifdef CONFIG_UART_FLOW_CONTRAL
    
#endif

    if ((rx_bytes >= 1) && (uart_info->wait_rx_flag == TRUE)){
        uart_info->wait_rx_flag = FALSE;
        tal_semaphore_post(uart_info->rx_block_sem);
    }

    return;
}


VOID_T uart_free_source(TAL_UART_DEV *uart_info)
{
    if (uart_info->rx_block_sem != NULL) {
        tal_semaphore_release(uart_info->rx_block_sem);
    }

    if (uart_info->tx_block_sem != NULL) {
        tal_semaphore_release(uart_info->tx_block_sem);
    }

#ifdef CONFIG_UART_ASYNC_WRITE
    if (uart_info->tx_ring != NULL) {
        tuya_ring_buff_free(uart_info->tx_ring);
    }

    if (uart_info->tx_ring_sem != NULL) {
        tal_semaphore_release(uart_info->tx_ring_sem);
    }
#endif

    if (uart_info->rx_ring != NULL) {
        tuya_ring_buff_free(uart_info->rx_ring);
    }

    if (uart_info->rx_ring_sem != NULL) {
        tal_semaphore_release(uart_info->rx_ring_sem);
    }

    tal_free(uart_info);
}

/**
* @brief init uart
*
* @param[in] port_num: uart port number
* @param[in] cfg: uart configure
*
* @note This API is used to init uart.
*
* @return the uart init result, 0, init success, other init error
*/
TUYA_WEAK_ATTRIBUTE  OPERATE_RET tal_uart_init(TUYA_UART_NUM_E port_num, TAL_UART_CFG_T *cfg)
{
    if (cfg == NULL) {
        return OPRT_INVALID_PARM;
    }

    OPERATE_RET ret = 0;

    if (g_uart_list.mutex == NULL) {
        ret = tal_mutex_create_init(&g_uart_list.mutex);
        if (ret != OPRT_OK) {
            return ret;
        }
    }

    TAL_UART_DEV *uart_info = uart_list_get_one_node(port_num);
    if (uart_info != NULL) { /* 已初始化，返回错误 */
        return OPRT_INVALID_PARM;
    }

    uart_info = tal_calloc(1, sizeof(TAL_UART_DEV));
    if (uart_info == NULL) {
        return OPRT_MALLOC_FAILED;
    }

    uart_info->port_num = port_num;
    uart_info->open_mode = cfg->open_mode;

    if (uart_info->open_mode & O_BLOCK) {
        ret = tal_semaphore_create_init(&uart_info->rx_block_sem, 0, 1);
        if (ret != OPRT_OK) {
            goto ERR_EXIT;
        }

        ret = tal_semaphore_create_init(&uart_info->tx_block_sem, 0, 1);
        if (ret != OPRT_OK) {
            goto ERR_EXIT;
        }
    }

    ret = tkl_uart_init(port_num, &cfg->base_cfg);
    if (ret != OPRT_OK) {
        goto ERR_EXIT;
    }

    ret = tuya_ring_buff_create(cfg->rx_buffer_size, OVERFLOW_STOP_TYPE, &uart_info->rx_ring);
    if (ret != OPRT_OK) {
        goto ERR_EXIT;
    }

    ret = tal_semaphore_create_init(&uart_info->rx_ring_sem, 1, 1);
    if (ret != OPRT_OK) {
        goto ERR_EXIT;
    }

#ifdef CONFIG_UART_ASYNC_WRITE
    tkl_uart_tx_irq_cb_reg(port_num, uart_tx_chars_in_isr);

    ret = tuya_ring_buff_create(cfg->tx_buffer_size, OVERFLOW_STOP_TYPE, &uart_info->tx_ring);
    if (ret != OPRT_OK) {
        goto ERR_EXIT;
    }

    ret = tal_semaphore_create_init(&uart_info->tx_ring_sem, 1, 1);
    if (ret != OPRT_OK) {
        goto ERR_EXIT;
    }
#endif

    ret = uart_list_add_one_node(uart_info);
    tkl_uart_rx_irq_cb_reg(port_num, uart_rx_chars_in_isr);

    return ret;

ERR_EXIT:
    uart_free_source(uart_info);
    return ret;
}

/**
* @brief read data from uart
*
* @param[in] port_num: uart port number
* @param[in] data: read data buffer
* @param[in] len: the read size
*
* @note This API is used to read data from uart.
*
* @return >=0, the read size; < 0, read error
*/
TUYA_WEAK_ATTRIBUTE OPERATE_RET tal_uart_read(TUYA_UART_NUM_E port_num, UINT8_T *data, UINT32_T len)
{
    if (data == NULL) {
        return OPRT_INVALID_PARM;
    }

    TAL_UART_DEV *uart_info = uart_list_get_one_node(port_num);
    if (uart_info == NULL) {
        return OPRT_INVALID_PARM;
    }

    OPERATE_RET ret = tal_semaphore_wait(uart_info->rx_ring_sem, SEM_WAIT_FOREVER);
    if (ret != OPRT_OK) {
        return ret;
    }

    TUYA_RINGBUFF_T *rx_ring = uart_info->rx_ring;
    UINT32_T buffer_size = tuya_ring_buff_used_size_get(rx_ring);
    UINT32_T read_count = 0;

    if (buffer_size != 0) {
        read_count = tuya_ring_buff_read(rx_ring, data, len);
    }
    else {
        if (uart_info->open_mode & O_BLOCK) {
            while(read_count == 0) {
                read_count = tuya_ring_buff_read(rx_ring, data, len);
                if (read_count != 0) {
                    break;
                }

                uart_info->wait_rx_flag = TRUE;
                ret = tal_semaphore_wait(uart_info->rx_block_sem, SEM_WAIT_FOREVER);
                if (ret != OPRT_OK) {
                    break;
                }
            }
        }
    }

#ifdef CONFIG_UART_FLOW_CONTRAL
    if (uart_info->open_mode & O_FLOW_CTRL) {
        ret = tuya_ring_buff_read(rx_ring);
        if (ret == 0) {
            tkl_uart_set_rx_flowctrl(port_num, FALSE);
        }
    }
#endif

    tal_semaphore_post(uart_info->rx_ring_sem);
    return read_count;
}

#ifdef CONFIG_UART_WRITE_ASYNC
TUYA_WEAK_ATTRIBUTE OPERATE_RET uart_async_write(TAL_UART_DEV *uart_info, UINT8_T *data, UINT32_T len)
{
    /* 可能存在并发写，需加锁互斥 */
    OPERATE_RET ret = tal_semaphore_wait(uart_info->tx_ring_sem, SEM_WAIT_FOREVER);
    if (uart_info == NULL) {
        return ret;
    }

    UINT16_T tx_bytes = tuya_ring_buff_write(uart_info->tx_ring, data, len);

#ifdef CONFIG_UART_BLOCK
    if (uart_info->open_mode & O_BLOCK) {
        while(ret == OPRT_OK)
            tx_bytes = tuya_ring_buff_write(uart_info->tx_ring, data, len);
            if (tx_bytes != 0) {
                break;
            }

            /* 缓存满，写不进去，需要进行阻塞 */
            uart_info->wait_tx_flag = TRUE;
            ret = tal_semaphore_wait(uart_info->tx_block_sem, SEM_WAIT_FOREVER);
        }
    }
#endif

    if (tx_bytes != 0) {
        set_tx_int(uart_info->port_num, true);
    }

    tal_semaphore_post(uart_info->tx_ring_sem);

    return tx_bytes;
}
#endif

/**
* @brief send data by uart
*
* @param[in] port_num: uart port number
* @param[in] data: send data buffer
* @param[in] len: the send size
*
* @note This API is used to send data by uart.
*
* @return >=0, the write size; < 0, write error
*/
TUYA_WEAK_ATTRIBUTE OPERATE_RET tal_uart_write(TUYA_UART_NUM_E port_num, CONST UINT8_T *data, UINT32_T len)
{
    if (data == NULL) {
        return OPRT_INVALID_PARM;
    }

    TAL_UART_DEV *uart_info = uart_list_get_one_node(port_num);
    if (uart_info == NULL) {
        return OPRT_INVALID_PARM;
    }

    INT_T tx_bytes = 0;
    INT_T ret;
    if ((uart_info->open_mode & O_ASYNC_WRITE) == 0) {
        while(tx_bytes != len) {
            ret = tkl_uart_write(port_num, (VOID_T *)&data[tx_bytes], 1);
            if (ret != 1) {
                break;
            }
            tx_bytes++;
        }
    }
#ifdef CONFIG_UART_WRITE_ASYNC
    else {
        tx_bytes = uart_async_write(uart_info, data, len);
    }
#endif

    return tx_bytes;
}


/**
* @brief deinit uart
*
* @param[in] port_num: uart port number
*
* @note This API is used to deinit uart.
*
* @return the uart deinit result, 0, deinit success, other deinit error
*/
TUYA_WEAK_ATTRIBUTE OPERATE_RET tal_uart_deinit(TUYA_UART_NUM_E port_num)
{
    TAL_UART_DEV *uart_info = uart_list_get_one_node(port_num);
    if (uart_info == NULL) {
        return OPRT_INVALID_PARM;
    }

    /**/
    OPERATE_RET ret = tkl_uart_deinit(port_num);
    if (ret != OPRT_OK) {
        return ret;
    }

    ret = uart_list_delete_one_node(uart_info);
    if (ret != OPRT_OK) {
        return ret;
    }

    tuya_ring_buff_free(uart_info->rx_ring);
    tal_semaphore_release(uart_info->rx_ring_sem);

#ifdef CONFIG_UART_ASYNC_WRITE    
    tuya_ring_buff_free(uart_info->tx_ring);
    tal_semaphore_release(uart_info->tx_ring_sem);
#endif


    if (uart_info->open_mode & O_BLOCK) {
        tal_semaphore_release(uart_info->rx_block_sem);
        tal_semaphore_release(uart_info->tx_block_sem);
    }

    /* 要对句柄进行释放 */
    tal_free(uart_info);

    return ret;
}


TUYA_WEAK_ATTRIBUTE INT_T tal_uart_get_rx_data_size(TUYA_UART_NUM_E port_num)
{
    TAL_UART_DEV *uart_info = uart_list_get_one_node(port_num);
    if (uart_info == NULL) {
        return OPRT_INVALID_PARM;
    }

    TUYA_RINGBUFF_T *rx_ring = uart_info->rx_ring;
    UINT32_T buffer_size = tuya_ring_buff_used_size_get(rx_ring);

    return buffer_size;
}
